package com.study.thread.juc.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
  重入锁ReentrantLock：非公平锁，默认是非公平锁，在构造函数传true则是使用公平锁，使用lock()方法进行同步锁定，unlock()方法解锁，需要配合try和finally使用，避免代码因为报错无法释放锁造成死锁
                      lock()：加锁，获取到锁当前线程继续运行，如果获取不到锁则进行等待，也就是当前线程通过LockSupport的park方法挂起，等待其余线程唤醒它来获取锁
                              1：如果锁线程重入，则count+1
                              唤醒：
                                 1、其它线程使用当前线程作为目标唤醒，一般就是锁释放的时候拿某一个线程唤醒，拿到锁返回true
                      lockInterruptibly()：为当前方法加锁，拿的锁则继续运行，如果获取不到锁则进行等待，也就是当前线程通过LockSupport的park方法挂起，等待其余线程唤醒它来获取锁
                               1：如果锁线程重入，则count+1
                               唤醒：
                                 1、其它线程使用当前线程作为目标唤醒，一般就是锁释放的时候拿某一个线程唤醒，拿到锁返回true
                                 2、其它线程调用了此等待线程的interrupt方法中断了
                      tryLock(long millis)：指定等待时间尝试获取当前线程的锁，获取到返回true，指定时间获取不到返回false，指定时间内进行等待休眠状态，以下三种情况会返回锁
                                            1、其它线程使用当前线程作为目标唤醒，一般就是锁释放的时候拿某一个线程唤醒，拿到锁返回true
                                            2、其它线程调用了当前线程的interupt方法中断了线程，则释放并抛出异常
                                            3、指定时间已经过了，拿不到锁返回false
                      tryLock()：尝试获取锁立马返回的，获取到返回true，获取不到返回false，获取到则需要释放锁，不会阻塞运行
                      unlock()：释放当前锁，并唤醒另一个线程，
                                1、如果线程没有获取重入锁，则释放锁，并唤醒线程，（如果当前线程获取的锁重入了，则先会把重入计数减去，当减到0释放锁，唤醒其它线程）
  与Synchronzed的区别：
                    1、sync是非公平锁，无法和ReentrantLock一样可以选择是用非公平锁还是公平锁
                        1.1：非公平锁是：很多线程获取锁被阻塞，获取锁的线程不是根据先来后到获取到的，sync是都会阻塞唤醒的时候是随机唤醒的，
                             ReentrantLock是获取到锁的线程可能释放锁但是还有人在等待，另一个线程恰好来获取锁，造成获取到锁的线程释放了锁，现在获取锁的线程获取到了锁，其余已经被阻塞的线程继续等待
                        1.2：公平锁是：ReentrantLock获取锁的时候看锁是否被占用，占用了则加入等待队列，当获取锁的线程释放锁的时候按照FIFO等待队列来释放线程
                    2、获取锁，释放锁
                        2.1：sync：是隐式的获取锁和释放锁，不需要自己操心获取锁释放锁，减少使用出错率，释放锁的规则按照后锁定的先释放，不能根据自己的要求灵活的获取锁释放锁
                        2.2：ReentrantLock：通过在代码里加lock获取锁和unlock来释放锁，使用上没有sync那么方便，但是可以使用灵活，可以先获取A->B->C,释放锁可以B->A->C 这样灵活的获取释放锁，
                    3、锁粗化，和锁消除，和优化
                        3.1：sync可以锁消除，锁粗化的优化，里面有偏向锁和自旋的功能优化
                        3.2：ReentrantLock：没有优化，还是需要获取锁，释放锁
                    4、获取锁的功能
                        4.1：sync: 没有额外获取锁的功能
                        4.2：ReentranLock：可以tryLock()尝试获取锁，并立马返回，可以tryLock(long millis)指定等待时间获取锁，
 */
public class ReentrantLockDemo {
    private static Lock lock = new ReentrantLock();


    public static void main(String[] args) {
        tryLock();
    }

    /**
     * lock方法的使用
     */
    private static void lock() {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "：执行");
                } finally {
                    lock.unlock(); //当注释时，线程2无法执行，因为锁没释放，一直执行线程1进行重入
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "：执行");
                } finally {
                    lock.unlock();
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程2").start();
    }

    /**
     * lock方法的使用
     */
    private static void tryLock() {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                if (lock.tryLock()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "：执行");
                    } finally {
                        lock.unlock(); //当注释时，线程2无法执行，因为锁没释放，一直执行线程1进行重入
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + "：未获取到锁");
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                if (lock.tryLock()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "：执行");
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + "：未获取到锁");
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程2").start();
    }

    /**
     * tryLock方法使用
     */
    private static void tryLockTime() {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "：执行");
                } finally {
                    lock.unlock(); //当注释时，线程2无法执行，因为锁没释放，一直执行线程1进行重入
                }
                try {
                    Thread.sleep(700);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                Boolean lockFlag = false;
                try {
                    lockFlag = lock.tryLock(50000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (lockFlag) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "：执行");
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + "：没有抢占到锁");
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程2").start();
    }
}
